Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

nice_things/test/test.macro.sh

The macros in this file implement a minimal unit testing and benchmarking framework.

This macro file is in a directory which is not automatically imported by nice_build. When building your tests, you must manually include this file in the build using the -m/--macro option, like in the following example, which builds all test files under the ./src/test/ directory:

for src_file in $(glob ./src/test/**/[!._~]*.sh); do
	./nice_build.sh -m nice_things/test/test.macro.sh -o "./build/${src_file#./src/}" "$src_file"
done

A typical test file starts by calling the test_init macro and importing the modules to be tested. Then the fail and or_fail macros are used to assert the result of each operation on the imported functions. At the end, depending on whether the test is being run in lenient_mode or in strict_mode, it is good practice to assert that those settings have not been accidentally changed by the tested functions using the is_lenient or the is_strict macro. Lastly, benchmarks can be added using the bench and end_bench pair of macros.

Here is a full example of test file using these macros:

#!/bin/sh
#{{{ strict_mode }}}
#{{{
test_init
import "{ hash_8bit }" from ./src/hash_8bit.sh
#}}}
expect=127
hash_8bit result 'known data' #{{{ or_fail }}}
[ "$expect" = "$result" ] #{{{ or_fail }}}

hash_8bit result 'random data' #{{{ or_fail }}}
case "$result" in "" | *[!0-9]* | 0[0-9]*)
	#{{{ fail }}}
	;;
esac
[ 0 -le "$result" ] && [ 255 -ge "$result" ] #{{{ or_fail }}}

#{{{ is_strict }}}

#{{{
bench 'hashing small data (36 bytes)' 1000
#}}}
c=0
while c=$((c + 1)) && [ "$c" -le 1000 ]; do
	hash_8bit result 'abcdefghijklmnopqrstuvwxyz1234567890'
done
#{{{ end_bench }}}

bench

Since 0.3.0 · Source

Synopsis
#{{{ bench <description> [<quantity>] }}}

Configuration

Description
This macro starts a benchmark block. Everything inside the benchmark block is executed in a sub-shell, and the total run-time is reported to stdout at the end.

The benchmark block must be ended with the accompanying end_bench macro. A test file can contain several benchmark blocks.

Benchmarks are only executed when the BENCH environment variable is set to a non-null value.

Options

Operands

  • <description>: Description of the benchmark.
  • <quantity>: Integer number of times the benchmark loops.

Stdin

Stdout
The benchmark report is printed to stdout.

Stderr

Exit status

  • 0: Successful completion.
  • >0: Benchmark failed to complete.

Abort
Aborts the process with an error status code if the benchmark fails to complete.

Usage examples

#{{{
bench 'invocation cost of my_function' 1000
#}}}
c=0
while c=$((c + 1)) && [ "$c" -le 1000 ]; do
	my_function
done
#{{{ end_bench }}}

end_bench

Since 0.3.0 · Source

Synopsis
#{{{ end_bench }}}

Configuration

Description
This macro ends a benchmark block started by the accompanying bench macro.

Options

Operands

Stdin

Stdout
The benchmark report is printed to stdout.

Stderr

Exit status

  • 0: Successful completion.
  • >0: Benchmark failed to complete.

Abort
Aborts the process with an error status code if the benchmark fails to complete.

Usage examples

#{{{ end_bench }}}

fail

Since 0.3.0 · Source

Synopsis
#{{{ fail }}}

Configuration

Description
Use this macro in a test failure condition.

Options

Operands

Stdin

Stdout
The failure report is printed to stdout.

Stderr

Exit status
>0: Test failed.

Abort
Always aborts the process with an error status code.

Usage examples

case $- in *f*) ;; *)
	#{{{ fail }}}
	;;
esac

is_lenient

Since 0.3.0 · Source

Synopsis
#{{{ is_lenient }}}

Configuration

Description
Assert that the shell options and value of the IFS variable conform to the lenient_mode defaults.

Some functions need to internally alter shell options or the value of the IFS shell-internal variable to do their work. Such functions must always revert these settings to their previous values before returning, otherwise they will create unintended side-effects that are very hard to debug. This and the accompanying is_strict macros exist to detect such side-effects and report it as a failure condition.

Options

Operands

Stdin

Stdout
The failure report is printed to stdout.

Stderr

Exit status

  • 0: Successful completion.
  • >0: Test failed.

Abort
Aborts the process with an error status code if the condition fails.

Usage examples

#{{{ is_lenient }}}

is_strict

Since 0.3.0 · Source

Synopsis
#{{{ is_strict }}}

Configuration

Description
Assert that the shell options and value of the IFS variable conform to the strict_mode.

Some functions need to internally alter shell options or the value of the IFS shell-internal variable to do their work. Such functions must always revert these settings to their previous values before returning, otherwise they will create unintended side-effects that are very hard to debug. This and the accompanying is_lenient macros exist to detect such side-effects and report it as a failure condition.

Options

Operands

Stdin

Stdout
The failure report is printed to stdout.

Stderr

Exit status

  • 0: Successful completion.
  • >0: Test failed.

Abort
Aborts the process with an error status code if the condition fails.

Usage examples

#{{{ is_strict }}}

or_fail

Since 0.3.0 · Source

Synopsis
#{{{ or_fail }}}

Configuration

Description
Use this macro to assert that the previous command succeeded.

This is a convenience macro. It prints the same code as the fail macro, preceded by an or-list operator (||).

Options

Operands

Stdin

Stdout
The failure report is printed to stdout.

Stderr

Exit status

  • 0: Successful completion.
  • >0: Test failed.

Abort
Aborts the process with the status code of the previous command if it returned a non-zero code.

Usage examples

expect='expected result'
result=$(some_function) #{{{ or_fail }}}
[ "$expect" = "$result" ] #{{{ or_fail }}}

test_init

Since 0.3.0 · Source

Synopsis
#{{{ test_init }}}

Configuration

Description
Import dependencies required by the test macros. Should be used at the top of the module with the other imports.

Options

Operands

Stdin

Stdout

Stderr

Exit status
0: Successful completion.

Abort

Usage examples

#!/bin/sh
#{{{
test_init
#}}}